
/* GCSx
** SCENEEDIT.CPP
**
** Scene support
** Expands basic Scene to include editor functionality
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

SceneEdit::SceneEdit(WorldEdit* myWorld, int myId) : Scene(myWorld, myId) { start_func
    if (myId) assert(myWorld);

    if (myId) {
        // Find an unused "Untitled" name
        string findName("scene");
        int pos = 1;
        while (myWorld->findScene(findName)) {
            findName = "scene ";
            findName += intToStr(++pos);
        }
    
        findName[0] = toupper(findName[0]);
        name = findName;
        headerModified = contentModified = 1;
    }
    else headerModified = contentModified = 0;
    
    nameL = name;
    toLower(nameL);
    
    for (int pos = 0; pos < MAX_LAYERS; ++pos) {
        layerEdits[pos] = NULL;
    }

    browserNode = NULL;
    disassociated = 0;
    desktop->broadcastObjChange(OBJ_SCENE | OBJMOD_CREATE, this, 0, 0);
}

SceneEdit::~SceneEdit() { start_func
    // @TODO: doesn't appear to be needed; no way to delete accidentally
    // if (!disassociated) desktop->broadcastObjChange(OBJ_SCENE | OBJMOD_DELETE, this, 0, 0);

    // World should be the one deleting us, which will delete our node also
    // Deleting layers[] will clear layerEdits[] also
}

void SceneEdit::setInfo(WorldEdit* myWorld, int newId) { start_func
    world = myWorld;
    id = newId;
}

void SceneEdit::disassociate() throw_File { start_func
    // Ensure not cached- our exception point
    cacheLoad();
    
    // Drop browser node
    dropBrowser();
    
    // Notify objects
    desktop->broadcastObjChange(OBJ_SCENE | OBJMOD_DELETE, this, 0, 0);
    
    disassociated = 1;
}

void SceneEdit::reassociate(TreeView* node) { start_func
    assert(disassociated);
    
    // Notify objects
    desktop->broadcastObjChange(OBJ_SCENE | OBJMOD_CREATE, this, 0, 0);
    
    // Browser node
    addToBrowser(node, 0);

    // Ensure written to file
    headerModified = contentModified = 1;
    disassociated = 0;
}

void SceneEdit::setHeaderModified() { start_func
    if (!headerModified) headerModified = 1;
}

void SceneEdit::setContentModified() { start_func
    if (!contentModified) {
        contentModified = 1;
        // No possibility of returning to cache
        delete cacheFile;
        cacheFile = NULL;
    }
}

LayerEdit* SceneEdit::getLayerEdit(int pos) { start_func
    assert(pos >= 0);
    assert(pos < layerCount);
    
    return layerEdits[pos];
}
   
void SceneEdit::dropBrowser() { start_func
    browserNode = NULL;
}

void SceneEdit::addToBrowser(TreeView* node, int makeCurrent) { start_func
    browserNode = new TreeView(name, this, 0, treeviewEventWrap);
    browserNode->setIcon(6);
    node->insert(browserNode, makeCurrent);
}

int SceneEdit::treeviewEvent(int code, int command, int check) { start_func
    assert(world);
    
    if (check) {
        if (command == EDIT_DELETE) return Window::COMMAND_ENABLE;
        return Window::COMMAND_HIDE;
    }

    switch (command) {
        case LV_SELECT:
            openEditWindow();
            return 1;
            
        case LV_RCLICK:
            // @TODO: Should actually be a popup with this as an item
            propertiesDialog(desktop->findPreviousFocusWindow());
            return 1;
        
        case EDIT_DELETE:
        case LV_DELETE:
            // Handles any error conditions/cancellation for us
            getWorldEdit()->deleteScene(this, desktop->findPreviousFocusWindow());
            return 1;
    }
    
    return 0;
}

int SceneEdit::treeviewEventWrap(void* ptr, int code, int command, int check) { start_func
    return ((SceneEdit*)ptr)->treeviewEvent(code, command, check);
}

int SceneEdit::insertLayer(class LayerEdit* newLayer, int pos, Window* srcWin, Window* exWin, int readd) throw_File { start_func
    assert(layerCount < MAX_LAYERS);
    assert(pos >= 0);
    assert(pos <= layerCount);
    assert(newLayer);
    
    int retValue = -1;

    // Lock it (do this first in case exception)
    // Only the first marklock might except, and it doesn't
    // need any of the below to be done prior as it doesn't
    // refer to scene or layer data to lock
    for (int lock = 0; lock < lockCount; ++lock) {
        newLayer->markLock(); // Exception point
    }
    
    setHeaderModified();
    setContentModified();
    
    // Undo
    if (world) {
        getWorldEdit()->indexLayer(newLayer);
        try {
            if (getWorldEdit()->undo.storeUndoLayerCreate(newLayer->getId(), id, pos, srcWin)) retValue = 1;
        }
        catch (UndoException& e) {
            getWorldEdit()->deindexLayer(newLayer);
            for (int lock = 0; lock < lockCount; ++lock) {
                newLayer->markUnlock();
            }
            return 0;
        }
    }

    // Move layers to make room
    for (int movePos = layerCount; movePos > pos; --movePos) {
        layers[movePos] = layers[movePos - 1];
        layerEdits[movePos] = layerEdits[movePos - 1];
    }
    
    // Insert new layer
    layers[pos] = layerEdits[pos] = newLayer;
    ++layerCount;
    if (readd) newLayer->reassociate();

    // Notify
    desktop->broadcastObjChange(OBJ_SCENE | OBJMOD_LAYERADD, this, pos, 0, exWin);
    return retValue;
}

int SceneEdit::deleteLayer(int pos, Window* srcWin, Window* exWin) throw_File { start_func
    assert(pos >= 0);
    assert(pos < layerCount);
    
    setHeaderModified();
    setContentModified();

    // Unlock it (file exception point)
    layerEdits[pos]->disassociate();
    for (int lock = 0; lock < lockCount; ++lock) {
        layers[pos]->markUnlock();
    }

    // Undo
    LayerEdit* delObj = layerEdits[pos];
    if (world) {
        try {
            if (getWorldEdit()->undo.storeUndoLayerDelete(layers[pos]->getId(), id, pos, layerEdits[pos], srcWin))
                delObj = NULL;
        }
        catch (UndoException& e) {
            // Relock it
            try {
                layerEdits[pos]->reassociate();
                for (int lock = 0; lock < lockCount; ++lock) {
                    layers[pos]->markLock();
                }
            }
            catch (FileException& e) {
                // Fatal because obviously we just unlocked it, we can't recover properly
                fatalCrash(0, "Error relocking layer after cancelling delete: %s", e.details);
            }

            return 0;
        }
    }
    
    // Delete
    if (world) getWorldEdit()->deindexLayer(layerEdits[pos]);
    layers[pos] = NULL;
    layerEdits[pos] = NULL;

    // Move layers to delete
    --layerCount;
    for (int movePos = pos; movePos < layerCount; ++movePos) {
        layers[movePos] = layers[movePos + 1];
        layerEdits[movePos] = layerEdits[movePos + 1];
    }
    layers[layerCount] = NULL;
    layerEdits[layerCount] = NULL;
    
    // @TODO: Some adjustment of sprites may be necessary
    
    // Notify of adjustment (delete had it's own notification)
    desktop->broadcastObjChange(OBJ_SCENE | OBJMOD_LAYERDEL, this, pos, 0, exWin);
    delete delObj;
    return 1;
}

void SceneEdit::swapLayer(int pos1, int pos2, Window* srcWin, Window* exWin) throw_Undo { start_func
    assert(pos1 >= 0);
    assert(pos1 < layerCount);
    assert(pos2 >= 0);
    assert(pos2 < layerCount);
    
    if (pos1 != pos2) {
        setHeaderModified();
        setContentModified(); // Because layers must be saved in different order now
        
        if (world) getWorldEdit()->undo.storeUndoLayerSwap(id, pos1, pos2, srcWin);
        
        swap(layers[pos1], layers[pos2]);
        swap(layerEdits[pos1], layerEdits[pos2]);

        // @TODO: Adjust sprites?
        
        desktop->broadcastObjChange(OBJ_SCENE | OBJMOD_LAYERMOVE, this, pos1, pos2, exWin);
    }
}

void SceneEdit::setName(const string& newName, Window* srcWin, Window* exWin) throw_Undo { start_func
    if (name != newName) {
        setHeaderModified();
        if (world) {
            getWorldEdit()->undo.storeUndoName(UndoBuffer::UNDO_SCENENAME, id, name, newName, srcWin);
            world->deindexScene(this);
        }
        name = newName;
        nameL = name;
        toLower(nameL);
        if (world) world->indexScene(this);
        if (browserNode) browserNode->changeName(newName);
    
        desktop->broadcastObjChange(OBJ_SCENE | OBJMOD_NAME, this, 0, 0, exWin);
    }
}

int SceneEdit::newLayer(Window* srcWin, Window* exWin) throw_File { start_func
    assert(world);
    
    LayerEdit* addLayer = NULL;
    LayerEdit* toDelete = NULL;
    int layerResult = 0;
    
    if (layerCount == MAX_LAYERS) {
        guiErrorBox("The maximum number of layers has been reached for this scene.", "Too Many Layers");
        return 1;
    }
    toDelete = addLayer = new LayerEdit(getWorldEdit(), this, getWorldEdit()->unusedLayerId());
    getWorldEdit()->undo.preUndoBlock();
    try {
        layerResult = insertLayer(addLayer, layerCount, srcWin, exWin); // Exception point
    }
    catch (...) {
        getWorldEdit()->undo.cancelUndoBlock();
        delete toDelete;
        throw;
    }
    // Undo successful- don't need to delete ourselves now
    if (layerResult > 0) toDelete = NULL;
    if (!layerResult) {
        getWorldEdit()->undo.cancelUndoBlock();
        delete toDelete;
        return 0;
    }
    if (!addLayer->propertiesDialog(1, Layer::LAYER_TILE, srcWin, exWin)) {
        try {
            // (deletes object too)
            if (toDelete) deleteLayer(layerCount - 1, srcWin, exWin);
        }
        catch (FileException& e) {
            fatalCrash(0, "File error undoing layer addition: %s", e.details);
        }
        getWorldEdit()->undo.cancelUndoBlock();
        return 0;
    }
    getWorldEdit()->undo.postUndoBlock();

    return 1;
}

const string SceneEdit::tileButton("\tTile");
const string SceneEdit::imageButton("\tImage");
const string SceneEdit::fontButton("\tFont");
const string SceneEdit::addLayerButton("\tAdd New Layer");

void SceneEdit::openEditWindow() { start_func
    try {
        // Add layer if no layers
        if (layerCount == 0) {
            if (!newLayer()) return; // Exception point
        }
        
        // Check layer types available
        int types = 0;
        Layer::LayerType edit; // Type to edit

        while (types == 0) {        
            int tileType = 0;
            int imageType = 0;
            int fontType = 0;

            for (int pos = 0; pos < layerCount; ++pos) {
                Layer::LayerType type = layers[pos]->getType();
                if (type != Layer::LAYER_EMPTY) edit = type;
            
                switch (type) {
                    case Layer::LAYER_TILE:
                        if (!tileType) ++types;
                        ++tileType;
                        break;
                    case Layer::LAYER_IMAGE:
                        if (!imageType) ++types;
                        ++imageType;
                        break;
                    case Layer::LAYER_FONT:
                        if (!fontType) ++types;
                        ++fontType;
                        break;
                    case Layer::LAYER_EMPTY:
                        break;
                }
            }
            
            // No non-empty layers?
            if (types == 0) {
                if (guiConfirmBox("All layers are empty layers- add a new layer?", "No Editable Layers")) {
                    if (!newLayer()) return; // Exception point
                }
                else return;
            }
        
            // Multiple types of layers? Choose type of layer to edit
            if (types > 1) {
                const string* button[3] = { NULL, NULL, NULL };

                if (tileType) button[0] = &tileButton;
                if (imageType) button[1] = &imageButton;
                if (fontType) button[2] = &fontButton;
                int result = guiMessageBox("Which type of layer would you like to edit?", "Multiple Layer Types",
                                           button[0], button[1], button[2], &addLayerButton, &messageBoxCancel);
                                           
                switch (result) {
                    case 0:
                    case 5:
                        return;
                
                    case 4:
                        if (!newLayer()) return; // Exception point
                        // (loop)
                        types = 0;
                        break;
                        
                    case 1:
                        edit = Layer::LAYER_TILE;
                        break;
                        
                    case 2:
                        edit = Layer::LAYER_IMAGE;
                        break;
                        
                    case 3:
                        edit = Layer::LAYER_FONT;
                        break;
                }
            }
        }
        
        switch (edit) {
            case Layer::LAYER_TILE:
                new SceneEditLayer(this);
                break;
            case Layer::LAYER_IMAGE:
                guiErrorBox("Image layer editing has not been implemented", "@TODO:");
                break;
            case Layer::LAYER_FONT:
                guiErrorBox("Font layer editing has not been implemented", "@TODO:");
                break;
            case Layer::LAYER_EMPTY:
                // (should nver reach here)
                assert(0);
                break;
        }
    }
    catch (FileException& e) {
        guiErrorBox(string(e.details), errorTitleFile);
    }
}

int SceneEdit::propertiesDialog(Window* srcWin, Window* exWin) { start_func
    string newName = name;

    if (ScenePropertiesDialog::create()->run(&newName, this)) {
        try {
            setName(newName, srcWin, exWin);
        }
        catch (UndoException& e) {
            return 0;
        }
        return 1;
    }
    
    return 0;
}

int SceneEdit::isHeaderModified() const { start_func
    return headerModified;
}

int SceneEdit::isContentModified() const { start_func
    return contentModified;
}

void SceneEdit::loadHeader(FileRead* file) throw_File { start_func
    assert(world);
    
    if (file->getVersion() > 1) {
        throw FileException("Unsupported scene version %d", file->getVersion());
    }

    file->readStr(name);
    nameL = name;
    toLower(nameL);
    layerCount = file->readInt();
    id = file->readInt();

    for (int pos = 0; pos < layerCount; ++pos) {
        layers[pos] = layerEdits[pos] = new LayerEdit(getWorldEdit(), this);
        layers[pos]->loadHeader(file);
    }
    
    // Check validity
    if ((layerCount > MAX_LAYERS) || (layerCount < 0) || (!id)) {
        throw FileException("Corrupted scene header");
    }
}

Uint32 SceneEdit::saveHeader(FileWrite* file) throw_File { start_func
    file->writeStr(name);
    file->writeInt(layerCount);
    file->writeInt(id);

    for (int pos = 0; pos < layerCount; ++pos) {
        layerEdits[pos]->saveHeader(file);
    }
    
    return 1;
}

void SceneEdit::saveContent(FileWrite* file) throw_File { start_func
    for (int pos = 0; pos < layerCount; ++pos) {
        assert(layerEdits[pos]);
        layerEdits[pos]->saveContent(file);
    }
}

void SceneEdit::saveSuccess() { start_func
    headerModified = 0;
    contentModified = 0;
    
    for (int pos = 0; pos < layerCount; ++pos) {
        assert(layerEdits[pos]);
        layerEdits[pos]->saveSuccess();
    }
}

void SceneEdit::cachedContent(FileRead* file, int oldData) { start_func
    // If already cached..
    if (cached) {
        delete cacheFile;
        cacheFile = file;
    }
    else if (oldData) {
        delete file;
    }
    else {
        // Cache ourselves?
        if (!lockCount) {
            delete cacheFile;
            cacheFile = file;
            for (int pos = 0; pos < layerCount; ++pos) {
                assert(layers[pos]);
                layers[pos]->recacheContent();
            }
            cached = 1;
        }
        else {
            delete file;
        }
    }
}

